package xyz.scootaloo.kami.server.service.impl

import io.vertx.core.Future
import xyz.scootaloo.kami.server.standard.currentTimeMillis
import xyz.scootaloo.kami.server.standard.getLogger
import xyz.scootaloo.kami.server.service.ApplicationStageListener
import xyz.scootaloo.kami.server.service.CacheService
import xyz.scootaloo.kami.server.service.CacheService.*
import xyz.scootaloo.kami.server.service.CrontabService
import xyz.scootaloo.kami.server.service.Sync
import java.util.*

/**
 * 一个想法, 假如不用redis, 就在jvm里面维护一个可以在多线程下安全访问的HashMap
 *
 * @author flutterdash@qq.com
 * @since 2022/2/2 0:27
 */
object InternalCacheServiceImpl : CacheService, ApplicationStageListener {

    private val log by lazy { getLogger() }

    private val crontabService = CrontabService()

    /**
     * 为什么使用TreeMap?
     *
     * 因为TreeMap继承了SortedMap, 当遍历map内元素时, 得到的是一个有序的序列;
     * 可以把这个序列看作是一个由小到大排列数组, 以当前时间为基准点, 这个基准点左边的是待删除元素,
     * 右边的是还未过期的元素; 这样可以遍历的时候, 将早于当前时间的元素删除,
     * 而遇到第一个晚于当前时间的元素时break跳出(在有序数组中, 如果下标i的元素比当前元素大, 那么后面的所有元素都比当前元素大);
     *
     * 这样, 可以有效的降低时间复杂度(不用每次都遍历整个集合)
     */
    private val expiryTimeMap = TreeMap<Long, KeyStore>()
    private val keyMap = HashMap<String, CacheItem>()

    override fun afterApplicationStarted() {
        crontabService.submit(CacheCrontab)
    }

    override fun putByte(key: String, value: Byte, expiry: Long) = Sync.run { doPut(key, value, expiry) }
    override fun putShort(key: String, value: Short, expiry: Long) = Sync.run { doPut(key, value, expiry) }
    override fun putInteger(key: String, value: Int, expiry: Long) = Sync.run { doPut(key, value, expiry) }
    override fun putLong(key: String, value: Long, expiry: Long) = Sync.run { doPut(key, value, expiry) }
    override fun putDouble(key: String, value: Double, expiry: Long) = Sync.run { doPut(key, value, expiry) }
    override fun putString(key: String, value: String, expiry: Long) = Sync.run { doPut(key, value, expiry) }
    override fun <T : Any> putObject(key: String, value: T, expiry: Long) = Sync.run { doPut(key, value, expiry) }

    override fun <T> getOrElse(key: String, expiry: Long, lazy: (CacheAccessor) -> T): Future<T> {
        return Sync { promise ->
            val existsValue = doGet<T>(key)
            if (existsValue == null) {
                val finalExpiryTime = if (expiry < 0) CacheService.defExpireTime else expiry
                val generated = lazy(CacheAccessorSingleton)
                doPut(key, generated as Any, finalExpiryTime)
                promise.complete(generated)
            } else {
                if (expiry > 0) {
                    doUpdateExpiryTime(key, expiry)
                }
                promise.complete(existsValue)
            }
        }
    }

    override fun getByte(key: String): Future<Byte?> = Sync { it.complete(doGet(key)) }
    override fun getShort(key: String): Future<Short?> = Sync { it.complete(doGet(key)) }
    override fun getInteger(key: String): Future<Int?> = Sync { it.complete(doGet(key)) }
    override fun getLong(key: String): Future<Long?> = Sync { it.complete(doGet(key)) }
    override fun getDouble(key: String): Future<Double?> = Sync { it.complete(doGet(key)) }
    override fun getString(key: String): Future<String?> = Sync { it.complete(doGet(key)) }
    override fun <T> get(key: String): Future<T?> = Sync { it.complete(doGet(key)) }

    override fun contains(key: String): Future<Boolean> = Sync { it.complete(doContains(key)) }

    override fun remove(key: String): Future<Unit> = Sync.run { doRemove(key) }

    override fun <T> access(block: (CacheAccessor) -> T): Future<T> {
        return Sync { promise ->
            promise.complete(block(CacheAccessorSingleton))
        }
    }

    override fun updateExpiryTime(key: String, newExpiryTime: Long) = Sync.run {
        doUpdateExpiryTime(key, newExpiryTime)
    }

    private fun doContains(key: String): Boolean {
        return keyMap[key] != null
    }

    private fun doPut(key: String, value: Any, expiryTime: Long) {
        val nextExpiryTime = currentTimeMillis() + expiryTime
        keyMap[key] = CacheItem(value, nextExpiryTime)
        if (expiryTime > 0) {
            storeExpireInfo(key, expiryTime)
        } else {
            log.warn("存储永久键: $key")
        }
    }

    private fun <T> doGet(key: String): T? {
        @Suppress("UNCHECKED_CAST")
        return keyMap[key]?.value as T?
    }

    private fun doRemove(key: String) {
        val item = keyMap[key]
        if (item != null) {
            keyMap.remove(key)
            removeExpiryInfo(key, item.expiryTime)
        }
    }

    private fun doUpdateExpiryTime(key: String, expiryTime: Long) {
        val newExpiryTime = currentTimeMillis() + expiryTime
        val desc = keyMap[key]
        if (desc != null) {
            val oldExpiryTime = desc.expiryTime
            if (oldExpiryTime != newExpiryTime) {
                removeExpiryInfo(key, oldExpiryTime)
                storeExpireInfo(key, newExpiryTime)
            }
        }
    }

    private fun storeExpireInfo(key: String, expiryTime: Long) {
        var expiryKeyStore = expiryTimeMap[expiryTime]
        if (expiryKeyStore != null) {
            expiryKeyStore.store(key)
        } else {
            expiryKeyStore = keyStore(key)
            expiryTimeMap[expiryTime] = expiryKeyStore
        }
    }

    private fun removeExpiryInfo(key: String, expiryTime: Long) {
        val keyStore = expiryTimeMap[expiryTime]
        if (keyStore != null) {
            keyStore.remove(key)
            if (keyStore.isEmpty()) {
                expiryTimeMap.remove(expiryTime)
            }
        }
    }

    private fun keyStore(key: String): KeyStore {
        return KeyStoreImpl(key)
    }

    object CacheCrontab : CrontabService.Crontab {

        override var valid = true
        override var delay = 100L
        override val name = "cache-service"
        override val order = CrontabService.Crontab.defOrder

        override fun run() {
            Sync.run { checkExpiryKeys() }
        }

        /**
         * 定时任务, 每[delay], 执行一次;
         * 清除已过期的键值对
         */
        private fun checkExpiryKeys() {
            if (expiryTimeMap.isEmpty() || keyMap.isEmpty())
                return

            val currentTime = currentTimeMillis()
            val invalidKeys = ArrayList<Long>()
            for ((expiryTime, keyStore) in expiryTimeMap) {
                if (expiryTime > currentTime)
                    break
                invalidKeys.add(expiryTime)
                doRemoveKeys(keyStore)
            }

            if (invalidKeys.isNotEmpty()) {
                for (key in invalidKeys) {
                    expiryTimeMap.remove(key)
                }
            }
        }

        private fun doRemoveKeys(keyStore: KeyStore) {
            for (key in keyStore.keys()) {
                keyMap.remove(key)
            }
        }
    }

    object CacheAccessorSingleton : CacheAccessor {
        override fun contains(key: String): Boolean {
            return doContains(key)
        }

        override fun <T> get(key: String): T {
            return doGet(key)!!
        }

        override fun set(key: String, value: Any, expiry: Long) {
            doPut(key, value, expiry)
        }

        override fun remove(key: String) {
            doRemove(key)
        }

        override fun updateExpiryTime(key: String, newExpiryTime: Long) {
            doUpdateExpiryTime(key, newExpiryTime)
        }
    }

    class KeyStoreImpl(
        var key: String?,
        var list: MutableList<String>? = null
    ) : KeyStore {

        override fun isEmpty(): Boolean {
            return (key == null && list == null)
        }

        override fun store(newKey: String) {
            if (key != null) {
                noDuplicateAdd(key!!)
                noDuplicateAdd(newKey)
                key = null
            } else {
                noDuplicateAdd(newKey)
            }
        }

        override fun keys(): List<String> {
            if (key != null) return listOf(key!!)
            return list!!
        }

        override fun remove(key: String) {
            if (this.key != null) {
                if (this.key == key)
                    this.key = null
            } else {
                removeKeyInList(key)
            }
        }

        private fun noDuplicateAdd(newKey: String) {
            list = list ?: ArrayList()
            for (item in list!!) {
                if (item == newKey)
                    return
            }
            list!!.add(newKey)
        }

        private fun removeKeyInList(key: String) {
            if (list == null)
                return
            val notnullList = list!!
            var keyIdx = -1
            for (idx in notnullList.indices) {
                if (key == notnullList[idx]) {
                    keyIdx = idx
                    break
                }
            }
            if (keyIdx != -1)
                notnullList.removeAt(keyIdx)
        }
    }
}